/**
* Global Sensor Networks (GSN) Source Code
* Copyright (c) 2006-2014, Ecole Polytechnique Federale de Lausanne (EPFL)
*
* This file is part of GSN.
*
* GSN is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GSN is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GSN. If not, see <http://www.gnu.org/licenses/>.
*
* File: gsn-tiny/src/tinygsn/storage/StorageManager.java
*
* @author Do Ngoc Hoan
*/


package tinygsn.storage;

import java.io.Serializable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import java.util.HashMap;

import tinygsn.beans.DataField;
import tinygsn.beans.DataTypes;
import tinygsn.beans.StreamElement;
import tinygsn.beans.StreamSource;
import tinygsn.model.vsensor.AbstractVirtualSensor;
import tinygsn.utils.GSNRuntimeException;
import android.annotation.SuppressLint;

public abstract class StorageManager {

	// deprecated
	protected boolean isH2;
	protected boolean isSQLite;
	private String databaseDriver;
	// private BasicDataSource pool;

	private Connection con = null;

	public void init(String databaseDriver, String username, String password,
			String databaseURL, int maxDBConnections) {
		this.databaseDriver = databaseDriver;
		try {
			initDatabaseAccess(con = getConnection());
		}
		catch (Exception e) {

		}
		finally {
			close(con);
		}
	}

	public void initDatabaseAccess(Connection con) throws Exception {
	}

	public abstract byte convertLocalTypeToGSN(int jdbcType, int precision);

	public abstract String getStatementDropIndex();

	public abstract String getStatementDropView();

	public abstract int getTableNotExistsErrNo();

	public abstract String addLimit(String query, int limit, int offset);

	public abstract StringBuilder getStatementUselessDataRemoval(
			String virtualSensorName, long storageSize);

	public byte convertLocalTypeToGSN(int jdbcType) {
		return convertLocalTypeToGSN(jdbcType, 0);
	}

	/**
	 * Returns false if the table doesn't exist. Uses the current default
	 * connection.
	 * 
	 * @param tableName
	 * @return False if the table doesn't exist in the current connection.
	 * @throws SQLException
	 */
	public boolean tableExists(CharSequence tableName) throws SQLException {
		Connection connection = null;
		try {
			connection = getConnection();
			return tableExists(tableName, new DataField[] {}, connection);
		}
		finally {
			close(connection);
		}
	}

	/**
	 * Checks to see if the given tablename exists using the given connection.
	 * 
	 * @param tableName
	 * @param connection
	 * @return
	 * @throws SQLException
	 */
	public boolean tableExists(CharSequence tableName, Connection connection)
			throws SQLException {
		return tableExists(tableName, new DataField[] {}, connection);
	}

	public abstract StringBuilder getStatementRemoveUselessDataCountBased(
			String virtualSensorName, long storageSize);

	public StringBuilder getStatementRemoveUselessDataTimeBased(
			String virtualSensorName, long storageSize) {
		StringBuilder query = null;
		// long timedToRemove = -1;
		// Connection conn = null;
		// try {
		// ResultSet rs =
		// Main.getStorage(virtualSensorName).executeQueryWithResultSet(new
		// StringBuilder("SELECT MAX(timed) FROM ").append(virtualSensorName), conn
		// = Main.getStorage(virtualSensorName).getConnection());
		// if (rs.next())
		// timedToRemove = rs.getLong(1);
		// } catch (SQLException e) {
		// logger.error(e.getMessage(), e);
		// } finally {
		// Main.getStorage(virtualSensorName).close(conn);
		// }
		// query = new
		// StringBuilder().append("delete from ").append(virtualSensorName).append(" where ").append(virtualSensorName).append(".timed < ").append(timedToRemove);
		// query.append(" - ").append(storageSize);
		return query;
	}

	public DataField[] tableToStructure(CharSequence tableName,
			Connection connection) throws SQLException {
		StringBuilder sb = new StringBuilder("select * from ").append(tableName)
				.append(" where 1=0 ");
		ResultSet rs = null;
		DataField[] toReturn = null;
		try {
			rs = executeQueryWithResultSet(sb, connection);
			ResultSetMetaData structure = rs.getMetaData();
			ArrayList<DataField> toReturnArr = new ArrayList<DataField>();
			for (int i = 1; i <= structure.getColumnCount(); i++) {
				String colName = structure.getColumnLabel(i);
				if (colName.equalsIgnoreCase("pk"))
					continue;
				int colType = structure.getColumnType(i);
				byte colTypeInGSN = convertLocalTypeToGSN(colType);
				toReturnArr.add(new DataField(colName, colTypeInGSN));
			}
			toReturn = toReturnArr.toArray(new DataField[] {});
		}
		finally {
			if (rs != null)
				close(rs);
		}
		return toReturn;
	}

	/*
	 * Alternative method to 'tableToStructure' Useful for correctly creating
	 * structure for fields with variable length (like char, varchar, binary,
	 * blob)
	 */
	public DataField[] tableToStructureByString(String tableName,
			Connection connection) throws SQLException {
		StringBuilder sb = new StringBuilder("select * from ").append(tableName)
				.append(" where 1=0 ");
		ResultSet rs = null;
		DataField[] toReturn = null;
		try {
			rs = executeQueryWithResultSet(sb, connection);
			ResultSetMetaData structure = rs.getMetaData();
			ArrayList<DataField> toReturnArr = new ArrayList<DataField>();
			for (int i = 1; i <= structure.getColumnCount(); i++) {
				String colName = structure.getColumnLabel(i);
				if (colName.equalsIgnoreCase("pk"))
					continue;
				if (colName.equalsIgnoreCase("timed"))
					continue;
				int colType = structure.getColumnType(i);
				String colTypeName = structure.getColumnTypeName(i);
				int precision = structure.getPrecision(i);
				byte colTypeInGSN = convertLocalTypeToGSN(colType);
				if ((colTypeInGSN == DataTypes.VARCHAR)
						|| (colTypeInGSN == DataTypes.CHAR))
					toReturnArr.add(new DataField(colName, colTypeName, precision,
							colName));
				else
					toReturnArr.add(new DataField(colName, colTypeInGSN));
			}
			toReturn = toReturnArr.toArray(new DataField[] {});
		}
		finally {
			if (rs != null)
				close(rs);
		}
		return toReturn;
	}

	/**
	 * Returns false if the table doesnt exist. If the table exists but the
	 * structure is not compatible with the specified fields the method throws
	 * GSNRuntimeException. Note that this method doesn't close the connection
	 * 
	 * @param tableName
	 * @param connection
	 *          (this method will not close it and the caller is responsible for
	 *          closing the connection)
	 * @return
	 * @throws SQLException
	 * @Throws GSNRuntimeException
	 */

	public boolean tableExists(CharSequence tableName, DataField[] fields,
			Connection connection) throws SQLException, GSNRuntimeException {
		// if (!ValidityTools.isValidJavaVariable(tableName))
		// throw new GSNRuntimeException("Table name is not valid");
		// StringBuilder sb = new
		// StringBuilder("select * from ").append(tableNameGeneratorInString(tableName)).append(" where 1=0 ");
		// ResultSet rs = null;
		// try {
		// rs = executeQueryWithResultSet(sb, connection);
		// ResultSetMetaData structure = rs.getMetaData();
		// if (fields != null && fields.length > 0)
		// nextField:for (DataField field : fields) {
		// for (int i = 1; i <= structure.getColumnCount(); i++) {
		// String colName = structure.getColumnLabel(i);
		// int colType = structure.getColumnType(i);
		// int colTypeScale = structure.getScale(i);
		// if (field.getName().equalsIgnoreCase(colName))
		// if (field.getDataTypeID() == convertLocalTypeToGSN(colType,
		// colTypeScale))
		// continue nextField;
		// else
		// throw new GSNRuntimeException("The column : "
		// + colName + " in the >" + tableName
		// + "< table is not compatible with type : "
		// + field.getType()
		// + ". The actual type for this table (currently in the database): " +
		// colType);
		// }
		// throw new GSNRuntimeException("The table " + tableName
		// + " in the database, doesn't have the >" + field.getName()
		// + "< column.");
		// }
		// } catch (SQLException e) {
		// if (e.getErrorCode() == getTableNotExistsErrNo() ||
		// e.getMessage().contains("does not exist"))
		// return false;
		// else {
		// // logger.error(e.getErrorCode());
		// throw e;
		// }
		// } finally {
		// close(rs);
		// }
		return true;
	}

	public boolean tableExists(CharSequence tableName, DataField[] fields)
			throws SQLException {
		Connection conn = null;
		boolean to_return = true;
		try {
			conn = getConnection();
			to_return = tableExists(tableName, fields, conn);
		}
		finally {
			close(conn);
		}
		return to_return;
	}

	/**
	 * Executes the query of the database. Returns the specified colIndex of the
	 * first row. Useful for image recovery of the web interface.
	 * 
	 * @param query
	 *          The query to be executed.
	 * @return A resultset with only one row and one column. The user of the
	 *         method should first call next on the result set to make sure that
	 *         the row is there and then retrieve the value for the row.
	 * @throws SQLException
	 */

	public ResultSet getBinaryFieldByQuery(StringBuilder query, String colName,
			long pk, Connection connection) throws SQLException {
		PreparedStatement ps = connection.prepareStatement(query.toString());
		ps.setLong(1, pk);
		return ps.executeQuery();
	}

	public void closeStatement(Statement stmt) {
		try {
			if (stmt != null) {
				stmt.close();
			}
		}
		catch (SQLException e) {
		}
	}

	public void close(ResultSet resultSet) {
		try {
			if (resultSet != null) {
				resultSet.close();
			}
		}
		catch (SQLException e) {
		}
	}

	public void close(PreparedStatement preparedStatement) {
		try {
			if (preparedStatement != null) {
				preparedStatement.close();
			}
		}
		catch (SQLException e) {
		}
	}

	public void close(Connection conn) {
		try {
			if (conn != null && !conn.isClosed()) {
				conn.close();
			}
		}
		catch (SQLException e) {
		}
	}

	/**
	 * ************************************************************************
	 * Various Statement Executors.
	 * ************************************************************************
	 */

	public void executeRenameTable(String oldName, String newName)
			throws SQLException {
		Connection conn = null;
		try {
			conn = getConnection();
			executeRenameTable(oldName, newName, conn);
		}
		finally {
			close(conn);
		}

	}

	public void executeRenameTable(String oldName, String newName,
			Connection connection) throws SQLException {
		PreparedStatement prepareStatement = null;
		try {
			prepareStatement = connection.prepareStatement(getStatementRenameTable(
					oldName, newName));
			prepareStatement.execute();
		}
		finally {
			close(prepareStatement);
		}

	}

	public void executeDropTable(CharSequence tableName) throws SQLException {
		Connection conn = null;
		try {
			conn = getConnection();
			executeDropTable(tableName, conn);
		}
		finally {
			close(conn);
		}
	}

	public void executeDropTable(CharSequence tableName, Connection connection) {
		PreparedStatement prepareStatement = null;
		try {
			String stmt = getStatementDropTable(tableName, connection).toString();
			prepareStatement = connection.prepareStatement(stmt);
			prepareStatement.execute();
		}
		catch (SQLException e) {
		}
	}

	public void executeCreateTable(CharSequence tableName, DataField[] structure,
			boolean unique) throws SQLException {
		Connection conn = null;
		try {
			conn = getConnection();
			executeCreateTable(tableName, structure, unique, conn);
		}
		finally {
			close(conn);
		}
	}

	/**
	 * Create a table with a index on the timed field.
	 * 
	 * @param tableName
	 * @param structure
	 * @param unique
	 *          , setting this true cause the system to create a unique index on
	 *          time.
	 * @param connection
	 * @throws SQLException
	 */
	public void executeCreateTable(CharSequence tableName, DataField[] structure,
			boolean unique, Connection connection) throws SQLException {
		StringBuilder sql = getStatementCreateTable(tableName, structure,
				connection);
		PreparedStatement prepareStatement = connection.prepareStatement(sql
				.toString());
		prepareStatement.execute();
		prepareStatement.close();
		sql = getStatementCreateIndexOnTimed(tableName, unique);
		prepareStatement = connection.prepareStatement(sql.toString());
		prepareStatement.execute();

	}

	public ResultSet executeQueryWithResultSet(StringBuilder query,
			Connection connection) throws SQLException {
		return connection.prepareStatement(query.toString()).executeQuery();
	}

	// public ResultSet executeQueryWithResultSet(AbstractQuery abstractQuery,
	// Connection c) throws SQLException {
	// if (abstractQuery.getLimitCriterion() == null) {
	// return executeQueryWithResultSet(abstractQuery.getStandardQuery(), c);
	// }
	// String query = addLimit(abstractQuery.getStandardQuery().toString(),
	// abstractQuery.getLimitCriterion().getSize(),
	// abstractQuery.getLimitCriterion().getOffset());
	// return executeQueryWithResultSet(new StringBuilder(query), c);
	// }

	// public DataEnumerator executeQuery(StringBuilder query, boolean
	// binaryFieldsLinked, Connection connection) throws SQLException {
	// // if (logger.isDebugEnabled())
	// // logger.debug("Executing query: " + query + "( Binary Field Linked:" +
	// binaryFieldsLinked + ")");
	// // return new DataEnumerator(this,
	// connection.prepareStatement(query.toString()), binaryFieldsLinked);
	// }
	//
	// /**
	// * Attention: Caller should close the connection.
	// *
	// * @param abstractQuery
	// * @param binaryFieldsLinked
	// * @param connection
	// * @return
	// * @throws SQLException
	// */
	// public DataEnumerator executeQuery(AbstractQuery abstractQuery, boolean
	// binaryFieldsLinked, Connection connection) throws SQLException {
	// if (abstractQuery.getLimitCriterion() == null) {
	// return executeQuery(abstractQuery.getStandardQuery(), binaryFieldsLinked,
	// connection);
	// }
	// String query = addLimit(abstractQuery.getStandardQuery().toString(),
	// abstractQuery.getLimitCriterion().getSize(),
	// abstractQuery.getLimitCriterion().getOffset());
	// if (logger.isDebugEnabled())
	// logger.debug("Executing query: " + query + "(" + binaryFieldsLinked + ")");
	// return new DataEnumerator(this,
	// connection.prepareStatement(query.toString()), binaryFieldsLinked);
	// }
	//
	// public DataEnumerator streamedExecuteQuery(AbstractQuery abstractQuery,
	// boolean binaryFieldsLinked, Connection connection) throws SQLException {
	// if (abstractQuery.getLimitCriterion() == null) {
	// return streamedExecuteQuery(abstractQuery.getStandardQuery().toString(),
	// binaryFieldsLinked, connection);
	// }
	// String query = addLimit(abstractQuery.getStandardQuery().toString(),
	// abstractQuery.getLimitCriterion().getSize(),
	// abstractQuery.getLimitCriterion().getOffset());
	// if (logger.isDebugEnabled())
	// logger.debug("Executing query: " + query + "(" + binaryFieldsLinked + ")");
	// return streamedExecuteQuery(query, binaryFieldsLinked, connection);
	// }
	//
	// public DataEnumerator executeQuery(StringBuilder query, boolean
	// binaryFieldsLinked) throws SQLException {
	// return executeQuery(query, binaryFieldsLinked, getConnection());
	// }
	//
	// public DataEnumerator streamedExecuteQuery(String query, boolean
	// binaryFieldsLinked, Connection conn) throws SQLException {
	// return new DataEnumerator(this, conn.prepareStatement(query),
	// binaryFieldsLinked);
	// }
	//
	//
	// public DataEnumerator streamedExecuteQuery(String query, boolean
	// binaryFieldsLinked) throws SQLException {
	// return streamedExecuteQuery(query, binaryFieldsLinked, getConnection());
	// }

	/**
	 * This method executes the provided statement over the connection. If there
	 * is an error returns -1 otherwise it returns the output of the executeUpdate
	 * method on the PreparedStatement class which reflects the number of changed
	 * rows in the underlying table.
	 * 
	 * @param sql
	 * @param connection
	 * @return Number of effected rows or -1 if there is an error.
	 */
	@SuppressLint("NewApi")
	public void executeCommand(String sql, Connection connection) {
		Statement stmt = null;
		try {
			stmt = connection.createStatement();
			stmt.execute(sql);
		}
		catch (SQLException error) {
		}
		finally {
			try {
				if (stmt != null && !stmt.isClosed())
					stmt.close();
			}
			catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}

	public int executeUpdate(String updateStatement, Connection connection) {
		int toReturn = -1;
		try {
			toReturn = connection.createStatement().executeUpdate(updateStatement);
		}
		catch (SQLException error) {
		}
		return toReturn;
	}

	public int executeUpdate(StringBuilder updateStatement, Connection connection) {

		int to_return = executeUpdate(updateStatement.toString(), connection);
		return to_return;
	}

	public int executeUpdate(StringBuilder updateStatement) throws SQLException {
		Connection connection = null;
		try {
			connection = getConnection();
			return executeUpdate(updateStatement, connection);
		}
		finally {
			close(connection);
		}
	}
	
	public void executeInsert(CharSequence tableName, DataField[] fields,
			StreamElement se) throws SQLException {
		Connection connection = null;
		try {
			connection = getConnection();
			executeInsert(tableName,fields, se, connection);
		}
		finally {
			close(connection);
		}
	}

	public void executeInsert(CharSequence tableName, DataField[] fields,
			StreamElement streamElement, Connection connection) throws SQLException {
		PreparedStatement ps = null;
		String query = getStatementInsert(tableName, fields).toString();
		try {
			ps = connection.prepareStatement(query);
			int counter = 1;
			for (DataField dataField : fields) {
				if (dataField.getName().equalsIgnoreCase("timed"))
					continue;
				Serializable value = streamElement.getData(dataField.getName());

				switch (dataField.getDataTypeID()) {
				case DataTypes.VARCHAR:
					if (value == null)
						ps.setNull(counter, Types.VARCHAR);
					else
						ps.setString(counter, value.toString());
					break;
				case DataTypes.CHAR:
					if (value == null)
						ps.setNull(counter, Types.CHAR);
					else
						ps.setString(counter, value.toString());
					break;
				case DataTypes.INTEGER:
					if (value == null)
						ps.setNull(counter, Types.INTEGER);
					else
						ps.setInt(counter, ((Number) value).intValue());
					break;
				case DataTypes.SMALLINT:
					if (value == null)
						ps.setNull(counter, Types.SMALLINT);
					else
						ps.setShort(counter, ((Number) value).shortValue());
					break;
				case DataTypes.TINYINT:
					if (value == null)
						ps.setNull(counter, Types.TINYINT);
					else
						ps.setByte(counter, ((Number) value).byteValue());
					break;
				case DataTypes.DOUBLE:
					if (value == null)
						ps.setNull(counter, Types.DOUBLE);
					else
						ps.setDouble(counter, ((Number) value).doubleValue());
					break;
				case DataTypes.BIGINT:
					if (value == null)
						ps.setNull(counter, Types.BIGINT);
					else
						ps.setLong(counter, ((Number) value).longValue());
					break;
				case DataTypes.BINARY:
					if (value == null)
						ps.setNull(counter, Types.BINARY);
					else
						ps.setBytes(counter, (byte[]) value);
					break;
				default:
					// logger.error("The type conversion is not supported for : "
					// + dataField.getName() + "(" + dataField.getDataTypeID() + ") : ");
				}
				counter++;
			}
			ps.setLong(counter, streamElement.getTimeStamp());
			ps.execute();
		}
		catch (GSNRuntimeException e) {
			// if (e.getType() ==
			// GSNRuntimeException.UNEXPECTED_VIRTUAL_SENSOR_REMOVAL) {
			// if (logger.isDebugEnabled())
			//
			// logger
			// .debug(
			// "An stream element dropped due to unexpected virtual sensor removal. (Stream element: "
			// + streamElement.toString() + ")+ Query: " + query, e);
			// } else
			// logger.warn(
			// "Inserting a stream element failed : " + streamElement.toString(), e);
		}
		catch (SQLException e) {
			if (e.getMessage().toLowerCase().contains("duplicate entry"))
				// logger
				// .info("Error occurred on inserting data to the database, an stream element dropped due to: "
				// + e.getMessage()
				// + ". (Stream element: "
				// + streamElement.toString() + ")+ Query: " + query);
				// else
				// logger
				// .warn("Error occurred on inserting data to the database, an stream element dropped due to: "
				// + e.getMessage()
				// + ". (Stream element: "
				// + streamElement.toString() + ")+ Query: " + query);
				throw e;
		}
		finally {
			close(ps);
		}
	}

	/***************************************************************************
	 * Statement Generators
	 **************************************************************************/
	/**
	 * Creates a sql statement which can be used for inserting the specified
	 * stream element in to the specified table.
	 * 
	 * @param tableName
	 *          The table which the generated sql will pointing to.
	 * @param fields
	 *          The stream element for which the sql statement is generated.
	 * @return A sql statement which can be used for inserting the provided stream
	 *         element into the specified table.
	 */
	public StringBuilder getStatementInsert(CharSequence tableName,
			DataField fields[]) {
		StringBuilder toReturn = new StringBuilder("insert into ")
				.append(tableName).append(" ( ");
		int numberOfQuestionMarks = 1; // Timed is always there.
		for (DataField dataField : fields) {
			if (dataField.getName().equalsIgnoreCase("timed"))
				continue;
			numberOfQuestionMarks++;
			toReturn.append(dataField.getName()).append(" ,");
		}
		toReturn.append(" timed ").append(" ) values (");
		for (int i = 1; i <= numberOfQuestionMarks; i++)
			toReturn.append("?,");
		toReturn.deleteCharAt(toReturn.length() - 1);
		toReturn.append(")");
		return toReturn;
	}

	public String getStatementRenameTable(String oldName, String newName) {
		return new StringBuilder("alter table ").append(oldName)
				.append(" rename to ").append(newName).toString();
	}

	public abstract StringBuilder getStatementDropTable(CharSequence tableName,
			Connection conn) throws SQLException;

	/**
	 * First detects the appropriate DB Engine to use. Get's the drop index
	 * statement syntax (which is DB dependent) and executes it.
	 * 
	 * @param indexName
	 * @param connection
	 * @return
	 * @throws SQLException
	 */
	public StringBuilder getStatementDropIndex(CharSequence indexName,
			CharSequence tableName, Connection connection) throws SQLException {
		return new StringBuilder(getStatementDropIndex()
				.replace("#NAME", indexName).replace("#TABLE", tableName));
	}

	public StringBuilder getStatementCreateIndexOnTimed(CharSequence tableName,
			boolean unique) throws SQLException {
		StringBuilder toReturn = new StringBuilder("CREATE ");
		if (unique)
			toReturn.append(" UNIQUE ");
		toReturn.append(" INDEX ")
				.append(tableNamePostFixAppender(tableName, "_INDEX")).append(" ON ")
				.append(tableName).append(" (timed DESC)");
		return toReturn;
	}

	public StringBuilder getStatementCreateTable(CharSequence tableName,
			DataField[] structure, Connection connection) throws SQLException {
		return getStatementCreateTable(tableName.toString(), structure);
	}

	public abstract StringBuilder getStatementCreateTable(String tableName,
			DataField[] structure);


	private String driver = null;

	/**
	 * The prefix is in lower case
	 * 
	 * @return
	 */
	public abstract String getJDBCPrefix();

	public String getJDBCDriverClass() {
		return databaseDriver;
	}

	/*
	 * Converts from internal GSN data types to a supported DB data type.
	 * 
	 * @param field The DataField to be converted @return convertedType The
	 * datatype name used by the target database.
	 */
	public abstract String convertGSNTypeToLocalType(DataField gsnType);

	/**
	 * Obtains the default database connection. The conneciton comes from the data
	 * source which is configured through gsn.xml file.
	 * 
	 * @return
	 * @throws SQLException
	 */
	public Connection getConnection() throws SQLException {
		return con;
	}

	/**
	 * Retruns an approximation of the difference between the current time of the
	 * DB and that of the local system
	 * 
	 * @return
	 */
	public long getTimeDifferenceInMillis() {
		String query = getStatementDifferenceTimeInMillis();
		Connection connection = null;
		try {
			connection = getConnection();
			PreparedStatement prepareStatement = connection.prepareStatement(query);
			long time1 = System.currentTimeMillis();
			ResultSet resultSet;
			resultSet = prepareStatement.executeQuery();
			resultSet.next();
			long time2 = System.currentTimeMillis();
			return resultSet.getLong(1) - time2 + (time2 - time1) / 2;
		}
		catch (SQLException error) {
			// logger.error(error.getMessage(), error);
		}
		finally {
			close(connection);
		}
		return 0;
	}

	public abstract String getStatementDifferenceTimeInMillis();

	//

	public String randomTableNameGenerator(int length) {
		byte oneCharacter;
		StringBuffer result = new StringBuffer(length);
		for (int i = 0; i < length; i++) {
			oneCharacter = (byte) ((Math.random() * ('z' - 'a' + 1)) + 'a');
			result.append((char) oneCharacter);
		}
		return result.toString();
	}

	public int tableNameGenerator() {
		return randomTableNameGenerator(15).hashCode();
	}

	public StringBuilder tableNameGeneratorInString(CharSequence tableName) {
		return new StringBuilder(tableName);
	}

	public StringBuilder tableNameGeneratorInString(int code) {
		StringBuilder sb = new StringBuilder("_");
		if (code < 0)
			sb.append("_");
		sb.append(Math.abs(code));
		return tableNameGeneratorInString(sb);
	}

	public String tableNamePostFixAppender(CharSequence table_name, String postFix) {
		String tableName = table_name.toString();
		if (tableName.endsWith("\""))
			return (tableName.substring(0, tableName.length() - 2)) + postFix + "\"";
		else
			return tableName + postFix;
	}

	public boolean isH2() {
		return isH2;
	}

	public boolean isSQLite() {
		return isSQLite;
	}

	public abstract ArrayList<AbstractVirtualSensor> getListofVS();
	
	public abstract ArrayList<StreamSource> getSourcesOfVS(String name);
	
	public abstract HashMap<String,String> getSetting(String keyPrefix);
		
	public abstract void setSetting(String key, String value);

}
